Dansk

Frigør potentialet i uforanderlige datastrukturer i TypeScript med readonly-typer. Lær at skabe mere forudsigelige, vedligeholdelsesvenlige og robuste applikationer ved at forhindre utilsigtede datamutationer.

TypeScript Readonly Typer: Mestring af uforanderlige datastrukturer

I det konstant udviklende landskab inden for softwareudvikling er jagten på robust, forudsigelig og vedligeholdelsesvenlig kode en evig bestræbelse. TypeScript, med sit stærke typesystem, tilbyder effektive værktøjer til at nå disse mål. Blandt disse værktøjer fremstår readonly-typer som en afgørende mekanisme til at håndhæve uforanderlighed (immutability), en hjørnesten i funktionel programmering og en nøgle til at bygge mere pålidelige applikationer.

Hvad er uforanderlighed, og hvorfor er det vigtigt?

Uforanderlighed betyder i sin kerne, at når et objekt er oprettet, kan dets tilstand ikke ændres. Dette enkle koncept har dybtgående konsekvenser for kodekvalitet og vedligeholdelse.

Readonly-typer i TypeScript: Dit arsenal til uforanderlighed

TypeScript tilbyder flere måder at håndhæve uforanderlighed på ved hjælp af nøgleordet readonly. Lad os udforske de forskellige teknikker, og hvordan de kan anvendes i praksis.

1. Readonly-egenskaber på interfaces og typer

Den mest ligetil måde at erklære en egenskab som readonly er ved at bruge nøgleordet readonly direkte i en interface- eller typedefinition.


interface Person {
  readonly id: string;
  name: string;
  age: number;
}

const person: Person = {
  id: "unique-id-123",
  name: "Alice",
  age: 30,
};

// person.id = "new-id"; // Fejl: Kan ikke tildele til 'id', fordi det er en skrivebeskyttet egenskab.
person.name = "Bob"; // Dette er tilladt

I dette eksempel er id-egenskaben erklæret som readonly. TypeScript vil forhindre ethvert forsøg på at ændre den, efter objektet er oprettet. Egenskaberne name og age, som mangler readonly-modifikatoren, kan frit ændres.

2. Utility-typen Readonly

TypeScript tilbyder en stærk utility-type kaldet Readonly<T>. Denne generiske type tager en eksisterende type T og transformerer den ved at gøre alle dens egenskaber readonly.


interface Point {
  x: number;
  y: number;
}

const point: Readonly<Point> = {
  x: 10,
  y: 20,
};

// point.x = 30; // Fejl: Kan ikke tildele til 'x', fordi det er en skrivebeskyttet egenskab.

Typen Readonly<Point> opretter en ny type, hvor både x og y er readonly. Dette er en praktisk måde hurtigt at gøre en eksisterende type uforanderlig.

3. Readonly-arrays (ReadonlyArray<T>) og readonly T[]

Arrays i JavaScript er i sagens natur foranderlige. TypeScript giver en måde at oprette readonly-arrays på ved hjælp af typen ReadonlyArray<T> eller den kortere notation readonly T[]. Dette forhindrer ændring af arrayets indhold.


const numbers: ReadonlyArray<number> = [1, 2, 3, 4, 5];
// numbers.push(6); // Fejl: Egenskaben 'push' eksisterer ikke på typen 'readonly number[]'.
// numbers[0] = 10; // Fejl: Indekssignatur i typen 'readonly number[]' tillader kun læsning.

const moreNumbers: readonly number[] = [6, 7, 8, 9, 10]; // Svarer til ReadonlyArray
// moreNumbers.push(11); // Fejl: Egenskaben 'push' eksisterer ikke på typen 'readonly number[]'.

Forsøg på at bruge metoder, der ændrer arrayet, såsom push, pop, splice, eller direkte tildeling til et indeks, vil resultere i en TypeScript-fejl.

4. const vs. readonly: Forstå forskellen

Det er vigtigt at skelne mellem const og readonly. const forhindrer gentildeling af selve variablen, mens readonly forhindrer ændring af objektets egenskaber. De tjener forskellige formål og kan bruges sammen for maksimal uforanderlighed.


const immutableNumber = 42;
// immutableNumber = 43; // Fejl: Kan ikke gentildele til const-variablen 'immutableNumber'.

const mutableObject = { value: 10 };
mutableObject.value = 20; // Dette er tilladt, fordi *objektet* ikke er const, kun variablen.

const readonlyObject: Readonly<{ value: number }> = { value: 30 };
// readonlyObject.value = 40; // Fejl: Kan ikke tildele til 'value', fordi det er en skrivebeskyttet egenskab.

const constReadonlyObject: Readonly<{ value: number }> = { value: 50 };
// constReadonlyObject = { value: 60 }; // Fejl: Kan ikke gentildele til const-variablen 'constReadonlyObject'.
// constReadonlyObject.value = 60; // Fejl: Kan ikke tildele til 'value', fordi det er en skrivebeskyttet egenskab.

Som vist ovenfor sikrer const, at variablen altid peger på det samme objekt i hukommelsen, hvorimod readonly garanterer, at objektets interne tilstand forbliver uændret.

Praktiske eksempler: Anvendelse af Readonly-typer i virkelige scenarier

Lad os udforske nogle praktiske eksempler på, hvordan readonly-typer kan bruges til at forbedre kodekvalitet og vedligeholdelse i forskellige scenarier.

1. Håndtering af konfigurationsdata

Konfigurationsdata indlæses ofte én gang ved applikationens opstart og bør ikke ændres under kørsel. Brug af readonly-typer sikrer, at disse data forbliver konsistente og forhindrer utilsigtede ændringer.


interface AppConfig {
  readonly apiUrl: string;
  readonly timeout: number;
  readonly features: readonly string[];
}

const config: AppConfig = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  features: ["featureA", "featureB"],
};

function fetchData(url: string, config: Readonly<AppConfig>) {
    // ... brug config.timeout og config.apiUrl sikkert, velvidende at de ikke vil ændre sig
}

fetchData("/data", config);

2. Implementering af Redux-lignende tilstandsstyring

I state management-biblioteker som Redux er uforanderlighed et kerneprincip. Readonly-typer kan bruges til at sikre, at tilstanden forbliver uforanderlig, og at reducere kun returnerer nye tilstandsobjekter i stedet for at ændre de eksisterende.


interface State {
  readonly count: number;
  readonly items: readonly string[];
}

const initialState: State = {
  count: 0,
  items: [],
};

function reducer(state: Readonly<State>, action: { type: string; payload?: any }): State {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 }; // Returner et nyt tilstandsobjekt
    case "ADD_ITEM":
      return { ...state, items: [...state.items, action.payload] }; // Returner et nyt tilstandsobjekt med opdaterede elementer
    default:
      return state;
  }
}

3. Arbejde med API-svar

Når man henter data fra et API, er det ofte ønskeligt at behandle svardata som uforanderlige, især hvis man bruger dem til at rendere UI-komponenter. Readonly-typer kan hjælpe med at forhindre utilsigtede mutationer af API-data.


interface ApiResponse {
  readonly userId: number;
  readonly id: number;
  readonly title: string;
  readonly completed: boolean;
}

async function fetchTodo(id: number): Promise<Readonly<ApiResponse>> {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
  const data: ApiResponse = await response.json();
  return data;
}

fetchTodo(1).then(todo => {
  console.log(todo.title);
  // todo.completed = true; // Fejl: Kan ikke tildele til 'completed', fordi det er en skrivebeskyttet egenskab.
});

4. Modellering af geografiske data (internationalt eksempel)

Overvej at repræsentere geografiske koordinater. Når en koordinat er sat, bør den ideelt set forblive konstant. Dette sikrer dataintegritet, især når man arbejder med følsomme applikationer som kortlægnings- eller navigationssystemer, der opererer på tværs af forskellige geografiske regioner (f.eks. GPS-koordinater for en leveringstjeneste, der dækker Nordamerika, Europa og Asien).


interface GeoCoordinates {
 readonly latitude: number;
 readonly longitude: number;
}

const tokyoCoordinates: GeoCoordinates = {
 latitude: 35.6895,
 longitude: 139.6917
};

const newYorkCoordinates: GeoCoordinates = {
 latitude: 40.7128,
 longitude: -74.0060
};


function calculateDistance(coord1: Readonly<GeoCoordinates>, coord2: Readonly<GeoCoordinates>): number {
 // Forestil dig en kompleks beregning ved hjælp af bredde- og længdegrad
 // Returnerer en pladsholderværdi for enkelhedens skyld
 return 1000; 
}

const distance = calculateDistance(tokyoCoordinates, newYorkCoordinates);
console.log("Afstand mellem Tokyo og New York (pladsholder):", distance);

// tokyoCoordinates.latitude = 36.0; // Fejl: Kan ikke tildele til 'latitude', fordi det er en skrivebeskyttet egenskab.

Dybt Readonly Typer: Håndtering af indlejrede objekter

Utility-typen Readonly<T> gør kun de direkte egenskaber af et objekt readonly. Hvis et objekt indeholder indlejrede objekter eller arrays, forbliver disse indlejrede strukturer foranderlige. For at opnå ægte dyb uforanderlighed skal du rekursivt anvende Readonly<T> på alle indlejrede egenskaber.

Her er et eksempel på, hvordan man opretter en dybt readonly type:


type DeepReadonly<T> = T extends (infer R)[]
  ? DeepReadonlyArray<R>
  : T extends object
  ? DeepReadonlyObject<T>
  : T;

interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}

type DeepReadonlyObject<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>;
};

interface Company {
  name: string;
  address: {
    street: string;
    city: string;
    country: string;
  };
  employees: string[];
}

const company: DeepReadonly<Company> = {
  name: "Example Corp",
  address: {
    street: "123 Main St",
    city: "Anytown",
    country: "USA",
  },
  employees: ["Alice", "Bob"],
};

// company.name = "New Corp"; // Fejl
// company.address.city = "New City"; // Fejl
// company.employees.push("Charlie"); // Fejl

Denne DeepReadonly<T>-type anvender rekursivt Readonly<T> på alle indlejrede egenskaber, hvilket sikrer, at hele objektstrukturen er uforanderlig.

Overvejelser og kompromiser

Selvom uforanderlighed giver betydelige fordele, er det vigtigt at være opmærksom på de potentielle kompromiser.

Biblioteker til uforanderlige datastrukturer

Flere biblioteker kan forenkle arbejdet med uforanderlige datastrukturer i TypeScript:

Bedste praksis for brug af Readonly-typer

For effektivt at udnytte readonly-typer i dine TypeScript-projekter, følg disse bedste praksisser:

Konklusion: Omfavnelse af uforanderlighed med TypeScript Readonly-typer

TypeScripts readonly-typer er et stærkt værktøj til at bygge mere forudsigelige, vedligeholdelsesvenlige og robuste applikationer. Ved at omfavne uforanderlighed kan du reducere risikoen for fejl, forenkle debugging og forbedre den overordnede kvalitet af din kode. Selvom der er nogle kompromiser at overveje, opvejer fordelene ved uforanderlighed ofte omkostningerne, især i komplekse og langvarige projekter. Når du fortsætter din TypeScript-rejse, gør readonly-typer til en central del af din udviklingsworkflow for at frigøre det fulde potentiale af uforanderlighed og bygge virkelig pålidelig software.

TypeScript Readonly Typer: Mestring af uforanderlige datastrukturer for robuste applikationer | MLOG